<!DOCTYPE html>
<title>Tests the CSSOM interfaces of @position-try rules</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#interfaces">
<link rel="author" href="mailto:xiaochengh@chromium.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id="anchor"></div>
<div id="other-anchor"></div>
<div id="target"></div>

<script>
function createStyle(t, text) {
  const style = document.createElement('style');
  style.textContent = text;
  t.add_cleanup(() => style.remove());
  document.head.appendChild(style);
  return style;
}

test(t => {
  const style = createStyle(
      t, '@position-try --pf { left: anchor(right); }');
  const positionTryRule = style.sheet.cssRules[0];
  assert_true(positionTryRule instanceof CSSPositionTryRule);
  assert_equals(positionTryRule.name, '--pf');
  assert_true(positionTryRule.style instanceof CSSPositionTryDescriptors);
  assert_equals(positionTryRule.style.length, 1);
  assert_equals(positionTryRule.style.left, 'anchor(right)');
}, 'CSSPositionTryRule attribute values');

test(t => {
  const style = createStyle(t, `
    @position-try --pf { top: anchor(top); left: 0; }
    #anchor, #other-anchor, #target {
      position: absolute; width: 100px; height: 100px;
    }
    #anchor { top: 100px; left: 0; anchor-name: --a; }
    #other-anchor { top: 200px; left: 0; anchor-name: --b; }
    #target { position-try-fallbacks: --pf; position-anchor: --a; left: 999999px; }
  `);
  const positionTryRule = style.sheet.cssRules[0];

  // Check the initial position fallback result
  assert_equals(target.getBoundingClientRect().left, 0);
  assert_equals(target.getBoundingClientRect().top, 100);

  // `left` is an allowed property in `@position-try` and should affect position fallback.
  positionTryRule.style.setProperty('left', 'anchor(right)');
  assert_equals(target.getBoundingClientRect().left, 100);
  assert_equals(target.getBoundingClientRect().top, 100);

  // This property are disallowed in `@position-try` rule, and hence should not affect
  // position fallback.
  positionTryRule.style.setProperty('position', 'static');
  assert_equals(target.getBoundingClientRect().left, 100);
  assert_equals(target.getBoundingClientRect().top, 100);

  // `position-anchor` is an allowed property in `@position-try` and should affect position fallback.
  positionTryRule.style.setProperty('position-anchor', '--b');
  assert_equals(target.getBoundingClientRect().left, 100);
  assert_equals(target.getBoundingClientRect().top, 200);

}, 'CSSPositionTryRule.style.setProperty setting allowed and disallowed properties');

test(t => {
  const style = createStyle(t, `
    @position-try --pf {
      top: 10px;
      left: 20px;
      --x: 200px;
      color: red;
    }
  `);
  let declarations = style.sheet.cssRules[0].style;
  assert_equals(declarations.length, 2);
  assert_equals(declarations.item(0), 'top');
  assert_equals(declarations.item(1), 'left');
}, 'CSSPositionTryDescriptors.item');

test(t => {
  const style = createStyle(t, '@position-try --pf {}');
  let declarations = style.sheet.cssRules[0].style;
  assert_equals(declarations.length, 0);
  declarations.cssText = `color:red;top:10px;`;
  assert_equals(declarations.length, 1);
}, 'CSSPositionTryDescriptors.cssText');

let supported_properties = [
  'margin',
  'margin-top',
  'margin-right',
  'margin-bottom',
  'margin-left',
  'margin-block',
  'margin-block-start',
  'margin-block-end',
  'margin-inline',
  'margin-inline-start',
  'margin-inline-end',
  'inset',
  'top',
  'left',
  'right',
  'bottom',
  'inset-block',
  'inset-block-start',
  'inset-block-end',
  'inset-inline',
  'inset-inline-start',
  'inset-inline-end',
  'width',
  'height',
  'min-width',
  'max-width',
  'min-height',
  'max-height',
  'block-size',
  'min-block-size',
  'max-block-size',
  'inline-size',
  'min-inline-size',
  'max-inline-size',
  'place-self',
  'align-self',
  'justify-self',
  'position-anchor',
  'position-area',
];

// A selection of unsupported properties.
let unsupported_properties = [
  'color',
  'align-items',
  'align-content',
  'background',
  'display',
  'position',
  'writing-mode',
  'direction',
  'syntax', // @property
];

let upperFirst = (x) => x[0].toUpperCase() + x.slice(1);
let lowerFirst = (x) => x[0].toLowerCase() + x.slice(1);
let toLowerCamelCase = (x) => lowerFirst(x.split('-').map(upperFirst).join(''));

// Test getting/setting the specified property on a CSSPositionTryDescriptors
// object. The property can either be supported or not supported,
// which determines the expected results.
function test_property(prop, supported) {
  test(t => {
    let decls = supported_properties.map(x => `${x}:unset;`).join('');
    let style = createStyle(t, `@position-try --pf { ${decls} }`);
    let declarations = style.sheet.cssRules[0].style;
    assert_equals(declarations.getPropertyValue(prop), supported ? 'unset' : '');
  }, `CSSPositionTryDescriptors.getPropertyValue(${prop})`);

  test(t => {
    let style = createStyle(t, '@position-try --pf {}');
    let declarations = style.sheet.cssRules[0].style;
    declarations.setProperty(prop, 'unset');
    assert_equals(declarations.getPropertyValue(prop), supported ? 'unset' : '');
  }, `CSSPositionTryDescriptors.setProperty(${prop})`);

  test(t => {
    let decls = supported_properties.map(x => `${x}:unset;`).join('');
    let style = createStyle(t, `@position-try --pf { ${decls} }`);
    let declarations = style.sheet.cssRules[0].style;
    assert_equals(declarations[prop], supported ? 'unset' : undefined);
  }, `CSSPositionTryDescriptors[${prop}] (set)`);

  test(t => {
    let style = createStyle(t, '@position-try --pf {}');
    let declarations = style.sheet.cssRules[0].style;
    declarations[prop] = 'unset';
    assert_equals(declarations.getPropertyValue(prop), supported ? 'unset' : '');
  }, `CSSPositionTryDescriptors[${prop}] (get)`);

  let camelCaseAttr = toLowerCamelCase(prop);
  if (camelCaseAttr != prop) {
    // Also test the camelCase version of the attribute.
    test(t => {
      let decls = supported_properties.map(x => `${x}:unset;`).join('');
      let style = createStyle(t, `@position-try --pf { ${decls} }`);
      let declarations = style.sheet.cssRules[0].style;
      assert_equals(declarations[camelCaseAttr], supported ? 'unset' : undefined);
    }, `CSSPositionTryDescriptors[${camelCaseAttr}] (get)`);

    test(t => {
      let style = createStyle(t, '@position-try --pf {}');
      let declarations = style.sheet.cssRules[0].style;
      declarations[camelCaseAttr] = 'unset';
      assert_equals(declarations.getPropertyValue(prop), supported ? 'unset' : '');
    }, `CSSPositionTryDescriptors[${camelCaseAttr}] (set)`);
  }
}

supported_properties.forEach(x => { test_property(x, /* supported */ true);  });
unsupported_properties.forEach(x => { test_property(x, /* supported */ false);  });

</script>
